/* * Copyright 2010, 2011 Ali Piccioni & Francois Poizat * * This program is distributed under the terms of the GNU General Public License * * This file is part of Team Liquid Android App. * * Team Liquid Android App is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Team Liquid Android App is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Team Liquid Android App. If not, see <http://www.gnu.org/licenses/>. * */ package org.opensourcetlapp.tl; import java.io.IOException; import java.util.Hashtable; import java.util.Iterator; import org.htmlcleaner.ContentNode; import org.htmlcleaner.TagNode; import org.htmlcleaner.XPatherException; import org.htmlcleaner.XmlSerializer; import org.opensourcetlapp.tl.widget.DisplayableLinkSpan; import org.opensourcetlapp.tl.widget.EditSpan; import org.opensourcetlapp.tl.widget.QuotePostSpan; import org.opensourcetlapp.tl.widget.SpoilerSpan; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.text.Editable; import android.text.Html; import android.text.Layout; import android.text.Spannable; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.method.MovementMethod; import android.text.style.AlignmentSpan; import android.text.style.BulletSpan; import android.text.style.ForegroundColorSpan; import android.text.style.LeadingMarginSpan; import android.text.style.QuoteSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.TextAppearanceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Log; import android.widget.TextView; import android.widget.TextView.BufferType; public class RenderUBB { private static final String TAG = "RenderUBB"; public TextView curTextView; private Editable curEditable; private SpoilerSpan curSpoilerSpan; private SpoilerSpan parentSpoilerSpan; private Context context; // THIS VARIABLE SHOULD NOT BE MADE STATIC FOR ANY REASON private XmlSerializer serializer; private CustomImageGetter imageGetter; private Hashtable<String, Integer> colorDictionary = new Hashtable<String, Integer>(); private TagNode node; private TextView globalTextView; public RenderUBB(Context context, TagNode node){ this.context = context; serializer = TLLib.getXmlSerializer(); //imageGetter = new CustomImageGetter(context); this.node = node; colorDictionary.put(" red", new Integer(Color.RED)); // The white space // is necessary colorDictionary.put(" blue", new Integer(Color.BLUE)); colorDictionary.put(" green", new Integer(Color.parseColor("#008800"))); } private void makeHeaderTextView() { curTextView = new TextView(context); curTextView.setBackgroundResource(R.drawable.header_background); curTextView.setTextColor(Color.BLACK); curTextView.setText("", BufferType.EDITABLE); curTextView.setPadding(5, 5, 5, 5); curEditable = (Editable) curTextView.getText(); MovementMethod m = curTextView.getMovementMethod(); if ((m == null) || !(m instanceof LinkMovementMethod)) { curTextView.setMovementMethod(LinkMovementMethod.getInstance()); } } private void makeTextView() { curTextView = new TextView(context); curTextView.setBackgroundColor(Color.parseColor("#d9dde0")); curTextView.setTextColor(Color.BLACK); curTextView.setText("", BufferType.EDITABLE); curTextView.setPadding(5, 5, 5, 5); curEditable = (Editable) curTextView.getText(); MovementMethod m = curTextView.getMovementMethod(); if ((m == null) || !(m instanceof LinkMovementMethod)) { curTextView.setMovementMethod(LinkMovementMethod.getInstance()); } } public void renderNewsHeader(Object newsHeaderNode, int postId, int topicId, int currentPage) throws IOException{ makeHeaderTextView(); TagNode header = (TagNode)newsHeaderNode; curTextView.append(Html.fromHtml(serializer.getXmlAsString(header), imageGetter, null)); } public void renderHeader(Object postHeaderNode, int postId, int topicId, int currentPage) throws IOException { makeHeaderTextView(); TagNode header = (TagNode) postHeaderNode; TagNode scriptNode = header.findElementByName("script", false); String script = ""; if (scriptNode != null){ // Needed to handle header images of Liquid`Nazgul and Evil Teletuby Iterator i = scriptNode.getChildren().iterator(); while (i.hasNext()){ script = i.next().toString(); if (script.contains("img")) { script = script.replace("\\", ""); int start = script.indexOf('\''); int end = script.indexOf('\'', start+1); script = script.substring(start+1, end); script = String.format("<img src=\"%s\" />", script); break; } } scriptNode.removeFromTree(); } try { TagNode spanNode = (TagNode)(header.evaluateXPath("./span")[0]); if (!spanNode.hasAttribute("class") || spanNode.getAttributeByName("class").equals("forummsginfoa")) { curTextView.setBackgroundColor(Color.parseColor("#3f5e8d")); } } catch (XPatherException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } int start; int end; String headerString = script + serializer.getXmlAsString(header); curTextView.append(Html.fromHtml(script)); renderTagNode(header); //curTextView.append(Html.fromHtml(headerString, // Does not recursively apply HTML formatting =( //imageGetter, null)); Editable editable = (Editable)curTextView.getText(); if (TLLib.loginStatus){ editable.append(" "); start = editable.length(); editable.append("Quote"); end = editable.length(); editable.setSpan(new QuotePostSpan(context, postId, topicId), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } if (TLLib.loginStatus && headerString.matches(String.format("(?idsmu:.*%s.*)", TLLib.loginName))){ editable.append(" "); start = editable.length(); editable.append("Edit"); end = editable.length(); editable.setSpan(new EditSpan(context, postId, topicId, currentPage), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } public void render() throws IOException{ render(node); } public void render(TagNode node) throws IOException { makeTextView(); globalTextView = curTextView; renderRec(node); } private void renderRec(Object node) throws IOException { if (node.getClass() == TagNode.class) { renderTagNode((TagNode) node); } else if (node.getClass() == ContentNode.class) { renderContentToken((ContentNode) node); } } private void renderChildren(TagNode node) throws IOException { for (Object nextNode : node.getChildren()) { renderRec(nextNode); } } private void renderTagNode(TagNode node) throws IOException { String nodeString = node.toString(); if (nodeString.equals("br")) { curEditable.append("\n"); renderChildren(node); } else if (nodeString.equals("b") || nodeString.equals("strong")) { int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); ((Spannable) curEditable).setSpan(new StyleSpan( android.graphics.Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); curEditable.append(" "); } else if (nodeString.equals("i")) { int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); ((Spannable) curEditable).setSpan(new StyleSpan( android.graphics.Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); curEditable.append(" "); } else if (nodeString.equals("font")) { int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); int size; if (node.hasAttribute("size")) { size = Integer.parseInt(node.getAttributeByName("size")); if (size == 4) { ((Spannable) curEditable).setSpan(new TextAppearanceSpan( null, 0, 20, null, null), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } if (node.hasAttribute("style")) { String style = node.getAttributeByName("style"); renderStyleAttribute(style, start, end); } } else if (nodeString.equals("div") && node.hasAttribute("id") && (node.getAttributeByName("id").contains("spoiler") || node.getAttributeByName("id").contains("Quote"))) { TextView temp = curTextView; Editable tempEditable = curEditable; makeTextView(); curEditable.append("\n"); SpoilerSpan tempSpoilerSpan = curSpoilerSpan; SpoilerSpan tempParentSpoilerSpan = parentSpoilerSpan; // In retrospect perhaps I should have just gone with an actually recursive implementation parentSpoilerSpan = curSpoilerSpan; renderChildren(node); curSpoilerSpan = tempSpoilerSpan; parentSpoilerSpan = tempParentSpoilerSpan; curSpoilerSpan.setTagNode(node); curSpoilerSpan.setCharSequence((CharSequence) curTextView.getText()); curTextView = temp; curEditable = tempEditable; } else if (nodeString.equals("div") && node.hasAttribute("class") && node.getAttributeByName("class").equals("quote")){ int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); ((Spannable) curEditable).setSpan(new QuoteSpan(R.color.TLBlueBlack), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ((Spannable) curEditable).setSpan(new LeadingMarginSpan.Standard(10), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ((Spannable) curEditable).setSpan(new TextAppearanceSpan(null, 0, 12, null, null), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (nodeString.equals("div")) { curEditable.append("\n"); int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); if (node.hasAttribute("style")) { String style = node.getAttributeByName("style"); renderStyleAttribute(style, start, end); } } else if (nodeString.equals("a")) { if (node.hasAttribute("onclick") ) { // spoiler tag String txt =""; //the spoiler 'a' tag contains two span like this <span>+ Show </span> Spoiler<span> + </span> if (node.getAttributeByName("onclick").contains("ShowSpoiler")) { Iterator itTagNode = node.getChildren().iterator(); TagNode firstSpan = (TagNode)itTagNode.next(); String middleString = HtmlTools.unescapeHtml(itTagNode.next().toString().trim()); TagNode thirdSpan = (TagNode)itTagNode.next(); txt= HtmlTools.unescapeHtml(firstSpan.getChildren().iterator().next().toString().trim()) + " " +middleString + " " +HtmlTools.unescapeHtml(thirdSpan.getChildren().iterator().next().toString().trim()); } else { //this is for the nested quotes txt = HtmlTools.unescapeHtml(node.getChildren().iterator().next().toString().trim()); } int start = curEditable.length(); curTextView.append(txt); int end = curEditable.length(); curSpoilerSpan = new SpoilerSpan(end, globalTextView); if (parentSpoilerSpan != null){ parentSpoilerSpan.spoilerSpanList.add(curSpoilerSpan); } ((Spannable) curEditable).setSpan(curSpoilerSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (node.hasAttribute("href")) { int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); String link = node.getAttributeByName("href"); if (DisplayableLinkSpan.isDisplayable(link)) { ((Spannable) curEditable).setSpan(new DisplayableLinkSpan( link, context), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else { if (link.charAt(0) == '/') { link = TLLib.getAbsoluteURL(link); } ((Spannable) curEditable).setSpan(new URLSpan(link), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } curTextView.append(" "); } } else if (nodeString.equals("img")) { if (Config.showImages) { if (node.getAttributeByName("src").charAt(0) == '/' || node.getAttributeByName("src").startsWith("http://www.teamliquid.net")) { curTextView.append(Html.fromHtml(serializer .getXmlAsString(node), imageGetter, null)); } else { String imageLink = "<a href=\"" + node.getAttributeByName("src") + "\">View Image</a><br/>"; curTextView.append(Html.fromHtml(imageLink)); } } } else if (nodeString.equals("center")) { int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); Spannable span = (Spannable) curEditable; span.setSpan(new AlignmentSpan.Standard( Layout.Alignment.ALIGN_CENTER), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (nodeString.equals("hr")) { curTextView.append("\n----------------\n"); } else if (nodeString.equals("p")) { curTextView.append("\n"); renderChildren(node); curTextView.append("\n"); } else if (nodeString.equals("object")) { String youtubeURL = node.getAttributeByName("data"); String imageLink = String.format("<a href=\"%s\">Watch Video</a>", youtubeURL); curTextView.append(Html.fromHtml(imageLink)); } else if (nodeString.equals("table") || nodeString.equals("tbody") || nodeString.equals("tr") || nodeString.equals("td")) { renderChildren(node); } else if (nodeString.equals("ul")) { curTextView.append("\n"); renderChildren(node); curTextView.append("\n"); } else if (nodeString.equals("li")) { int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); Spannable span = (Spannable) curEditable; span.setSpan(new BulletSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (nodeString.equals("s")) { int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); ((Spannable) curEditable).setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); curTextView.append(" "); } else if (nodeString.equals("u")){ int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); ((Spannable) curEditable).setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); curTextView.append(" "); } else if (nodeString.equals("span")){ int start = curEditable.length(); renderChildren(node); int end = curEditable.length(); if (node.hasAttribute("style")){ renderStyleAttribute(node.getAttributeByName("style"), start, end); } } else { Spanned span = Html.fromHtml(node.toString(), null, null); curTextView.append(span); renderChildren(node); } } private void renderStyleAttribute(String style, int start, int end) { String[] styleList = style.split(";"); for (String s : styleList) { String[] styleInfo = s.split(":"); if (styleInfo[0].trim().equals("color")) { if (colorDictionary.containsKey(styleInfo[1])) { ((Spannable) curEditable).setSpan(new ForegroundColorSpan( colorDictionary.get(styleInfo[1]).intValue()), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (styleInfo[1].contains("#")){ styleInfo[1] = styleInfo[1].trim(); if (styleInfo[1].length() == 4){ String r, g, b; r = styleInfo[1].charAt(1)+""; g = styleInfo[1].charAt(2)+""; b = styleInfo[1].charAt(3)+""; String colorString = "#" + r + r + g + g + b + b; ((Spannable) curEditable).setSpan(new ForegroundColorSpan( Color.parseColor(colorString)), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); }else { ((Spannable) curEditable).setSpan(new ForegroundColorSpan( Color.parseColor(styleInfo[1].trim())), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } } else if (styleInfo[0].trim().equals("text-align")) { Spannable span = (Spannable) curEditable; span.setSpan(new AlignmentSpan.Standard( Layout.Alignment.ALIGN_CENTER), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (styleInfo[0].trim().equals("float")) { if (styleInfo[1].trim().equals("right")) { Spannable span = (Spannable) curEditable; span.setSpan(new AlignmentSpan.Standard( Layout.Alignment.ALIGN_OPPOSITE), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } else if (styleInfo[0].trim().equals("font-size")) { try { int size = Integer.parseInt(((styleInfo[1].split("p"))[0]) .trim()); Spannable span = (Spannable) curEditable; ((Spannable) curEditable).setSpan(new TextAppearanceSpan(null, 0, size, null, null), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } catch (NumberFormatException e){ } } } } private void renderContentToken(ContentNode token) { ContentNode childToken = (ContentNode) token; Spanned span = Html.fromHtml(token.toString(), null, null); curTextView.append(span); } }